// @refresh reset
import BaseThree from '@/three';
import { useEffect, useRef } from 'react';
import { BoxGeometry, Mesh, MeshBasicMaterial, PlaneGeometry, ShaderMaterial } from 'three';
import * as THREE from 'three'
import { FileThree } from '@/three/utils/index';
import { extendMaterial } from './ExtendMaterial';

class KuriThree extends BaseThree {
  constructor(dom: HTMLDivElement) {
    super(dom)
    this.camera.position.set(0.7, 0.368, 1.79);

    this.renderer.shadowMap.enabled = true;
    this.scene.background = new THREE.Color(0x333333);

    // this.animatePrivitivesByShaderTime()
    // this.animateSpherePrimitives()

    this.animateModelPrimitives()
    this.addPlane()
    this.addLight()
  }

  async animateModelPrimitives() {
    const g = new THREE.Group();
    g.rotation.set(Math.PI / 2, 0, Math.PI)
    g.scale.setScalar(0.3);
    g.position.y = 0.3;
    this.scene.add(g);

    const data = { progress: 0. }
    const gui = await this.utils.getGUIInstance();
    const folder = gui.addFolder('setting');
    folder.add(data, 'progress', 0, 1)
      .onChange((val: number) => {
        mat.uniforms.uProgress.value = val;
      });

    const mat = extendMaterial( THREE.MeshStandardMaterial, {
      // class: THREE.CustomMaterial,  // In this case ShaderMaterial would be fine too, just for some features such as envMap this is required

      vertexHeader: `
        attribute float aRandom;
        attribute vec3 aCenter;
        uniform float uTime;
        uniform float uProgress;

        mat4 rotationMatrix(vec3 axis, float angle) {
          axis = normalize(axis);
          float s = sin(angle);
          float c = cos(angle);
          float oc = 1.0 - c;
          
          return mat4(oc * axis.x * axis.x + c,           oc * axis.x * axis.y - axis.z * s,  oc * axis.z * axis.x + axis.y * s,  0.0,
                      oc * axis.x * axis.y + axis.z * s,  oc * axis.y * axis.y + c,           oc * axis.y * axis.z - axis.x * s,  0.0,
                      oc * axis.z * axis.x - axis.y * s,  oc * axis.y * axis.z + axis.x * s,  oc * axis.z * axis.z + c,           0.0,
                      0.0,                                0.0,                                0.0,                                1.0
                );
        }
        
        vec3 rotate(vec3 v, vec3 axis, float angle) {
          mat4 m = rotationMatrix(axis, angle);
          return (m * vec4(v, 1.0)).xyz;
        }
      `,
      vertex: {
        transformEnd: `
          // 图元 沿法线 方向 膨胀
          // transformed += uProgress * clamp((aRandom - .9), 0.0, 1.0) * normal * 12.0;
          transformed += uProgress * clamp((aRandom - .8), 0.0, 1.0) * 16.0;


          // 每个图元沿其中心缩放 + 自底向上缩放显示
          transformed = transformed - aCenter;
          transformed *= (1.0 - uProgress);
          transformed += aCenter;


          // 每个图元，随机沿 y 轴旋转
          transformed = rotate(
            transformed,
            vec3(0.0, 0.0, 1.0),
            aRandom * uProgress * PI * 3.0
          );
        `
      },
    
      uniforms: {
        uTime: {
          mixed: true,    // Uniform will be passed to a derivative material (MeshDepthMaterial below)
          linked: true,   // Similar as shared, but only for derivative materials, so wavingMaterial will have it's own, but share with it's shadow material
          value: 0
        },
        uProgress: {
          mixed: true,    // Uniform will be passed to a derivative material (MeshDepthMaterial below)
          linked: true,   // Similar as shared, but only for derivative materials, so wavingMaterial will have it's own, but share with it's shadow material
          value: data.progress
        },
        diffuse: {
          value: new THREE.Color(0xc8c8c8)
        }
      }
    
    } );

    const targets = [];
    const glb = await FileThree.loadDracoGLTF('/models/head-sculp.glb');

    for (let i = 4; i <= 17; i++) {
      // 4-17
      const target = glb.scene.getObjectByName(`Object_${i}`)
      targets.push(target)
    }

    targets.forEach(mesh => {

      const geo = (mesh as Mesh).geometry.toNonIndexed();
  
      // 1. 求每个 triangle 的 center
      // 2. 给每个 triangle 一个 random 值
      { 
        const len = geo.attributes.position.count;
  
        const randoms = new Float32Array(len);
        const centers = new Float32Array(len * 3);
        for (let i = 0; i < len; i += 3) {
          const r = Math.random();
          randoms[i] = r;
          randoms[i + 1] = r;
          randoms[i + 2] = r;
  
          const x1 = geo.attributes.position.array[i * 3 + 0];
          const y1 = geo.attributes.position.array[i * 3 + 1];
          const z1 = geo.attributes.position.array[i * 3 + 2];
          const x2 = geo.attributes.position.array[i * 3 + 3];
          const y2 = geo.attributes.position.array[i * 3 + 4];
          const z2 = geo.attributes.position.array[i * 3 + 5];
          const x3 = geo.attributes.position.array[i * 3 + 6];
          const y3 = geo.attributes.position.array[i * 3 + 7];
          const z3 = geo.attributes.position.array[i * 3 + 8];
  
          const center = new THREE.Vector3(x1, y1, z1)
            .add(new THREE.Vector3(x2, y2, z2))
            .add(new THREE.Vector3(x3, y3, z3))
            .divideScalar(3);
  
          centers.set([center.x, center.y, center.z], i * 3);
          centers.set([center.x, center.y, center.z], (i + 1) * 3);
          centers.set([center.x, center.y, center.z], (i + 2) * 3);
        }
        geo.setAttribute('aRandom', new THREE.BufferAttribute(randoms, 1));
        geo.setAttribute('aCenter', new THREE.BufferAttribute(centers, 3));
      }
  
      const target = new Mesh(geo, mat);
      target.castShadow = true;
  
      // ** customDepthMaterial，指定 该 mesh 的 depthTest 信息
      // ** 将 primitive 的 vertex 动画信息与 depthTest信息 同步
      target.customDepthMaterial = extendMaterial(THREE.MeshDepthMaterial, {
        template: mat
      })
      g.add(target);
    })
  }

  async animateSpherePrimitives() {
    const data = { progress: 0.5 }
    const gui = await this.utils.getGUIInstance();
    const folder = gui.addFolder('setting');
    folder.add(data, 'progress', 0, 1)
      .onChange((val: number) => {
        mat.uniforms.uProgress.value = val;
      });

    const geo = new THREE.SphereGeometry(1, 32, 32)
      .toNonIndexed();

    // 1. 求每个 triangle 的 center
    // 2. 给每个 triangle 一个 random 值
    { 
      const len = geo.attributes.position.count;

      const randoms = new Float32Array(len);
      const centers = new Float32Array(len * 3);
      for (let i = 0; i < len; i += 3) {
        const r = Math.random();
        randoms[i] = r;
        randoms[i + 1] = r;
        randoms[i + 2] = r;

        const x1 = geo.attributes.position.array[i * 3 + 0];
        const y1 = geo.attributes.position.array[i * 3 + 1];
        const z1 = geo.attributes.position.array[i * 3 + 2];
        const x2 = geo.attributes.position.array[i * 3 + 3];
        const y2 = geo.attributes.position.array[i * 3 + 4];
        const z2 = geo.attributes.position.array[i * 3 + 5];
        const x3 = geo.attributes.position.array[i * 3 + 6];
        const y3 = geo.attributes.position.array[i * 3 + 7];
        const z3 = geo.attributes.position.array[i * 3 + 8];

        const center = new THREE.Vector3(x1, y1, z1)
          .add(new THREE.Vector3(x2, y2, z2))
          .add(new THREE.Vector3(x3, y3, z3))
          .divideScalar(3);

        centers.set([center.x, center.y, center.z], i * 3);
        centers.set([center.x, center.y, center.z], (i + 1) * 3);
        centers.set([center.x, center.y, center.z], (i + 2) * 3);
      }
      geo.setAttribute('aRandom', new THREE.BufferAttribute(randoms, 1));
      geo.setAttribute('aCenter', new THREE.BufferAttribute(centers, 3));
    }

    const mat = extendMaterial( THREE.MeshStandardMaterial, {
      // class: THREE.CustomMaterial,  // In this case ShaderMaterial would be fine too, just for some features such as envMap this is required

      vertexHeader: `
        attribute float aRandom;
        attribute vec3 aCenter;
        uniform float uTime;
        uniform float uProgress;

        mat4 rotationMatrix(vec3 axis, float angle) {
          axis = normalize(axis);
          float s = sin(angle);
          float c = cos(angle);
          float oc = 1.0 - c;
          
          return mat4(oc * axis.x * axis.x + c,           oc * axis.x * axis.y - axis.z * s,  oc * axis.z * axis.x + axis.y * s,  0.0,
                      oc * axis.x * axis.y + axis.z * s,  oc * axis.y * axis.y + c,           oc * axis.y * axis.z - axis.x * s,  0.0,
                      oc * axis.z * axis.x - axis.y * s,  oc * axis.y * axis.z + axis.x * s,  oc * axis.z * axis.z + c,           0.0,
                      0.0,                                0.0,                                0.0,                                1.0
                );
        }
        
        vec3 rotate(vec3 v, vec3 axis, float angle) {
          mat4 m = rotationMatrix(axis, angle);
          return (m * vec4(v, 1.0)).xyz;
        }
      `,
      vertex: {
        transformEnd: `
          // 每个图元沿其中心缩放 + 自底向上缩放显示
          float prog = (position.y + 1.) / 2.;
          float locProg = clamp((uProgress - 0.8 * prog) / 0.2, 0.0, 1.0);
          transformed = transformed - aCenter;
          transformed += 3. * normal * aRandom * locProg;
          transformed *= (1.0 - locProg);
          transformed += aCenter;


          // 每个图元，随机沿 y 轴旋转
          transformed = rotate(
            transformed,
            vec3(0.0, 1.0, 0.0),
            aRandom * locProg * PI * 1.0
          );


          // 每个 三角形，沿 法线方向 动画
          // transformed += (1.0 - uProgress) * aRandom * normal;
        `
      },
    
      uniforms: {
        uTime: {
          mixed: true,    // Uniform will be passed to a derivative material (MeshDepthMaterial below)
          linked: true,   // Similar as shared, but only for derivative materials, so wavingMaterial will have it's own, but share with it's shadow material
          value: 0
        },
        uProgress: {
          mixed: true,    // Uniform will be passed to a derivative material (MeshDepthMaterial below)
          linked: true,   // Similar as shared, but only for derivative materials, so wavingMaterial will have it's own, but share with it's shadow material
          value: data.progress
        },
        diffuse: {
          value: new THREE.Color(0xee3399)
        }
      }
    } );

    const mat1 = extendMaterial( THREE.MeshStandardMaterial, {
      // class: THREE.CustomMaterial,  // In this case ShaderMaterial would be fine too, just for some features such as envMap this is required

      vertexHeader: `
        attribute float aRandom;
        attribute vec3 aCenter;
        uniform float uTime;
        uniform float uProgress;

        mat4 rotationMatrix(vec3 axis, float angle) {
          axis = normalize(axis);
          float s = sin(angle);
          float c = cos(angle);
          float oc = 1.0 - c;
          
          return mat4(oc * axis.x * axis.x + c,           oc * axis.x * axis.y - axis.z * s,  oc * axis.z * axis.x + axis.y * s,  0.0,
                      oc * axis.x * axis.y + axis.z * s,  oc * axis.y * axis.y + c,           oc * axis.y * axis.z - axis.x * s,  0.0,
                      oc * axis.z * axis.x - axis.y * s,  oc * axis.y * axis.z + axis.x * s,  oc * axis.z * axis.z + c,           0.0,
                      0.0,                                0.0,                                0.0,                                1.0
                );
        }
        
        vec3 rotate(vec3 v, vec3 axis, float angle) {
          mat4 m = rotationMatrix(axis, angle);
          return (m * vec4(v, 1.0)).xyz;
        }
      `,
      vertex: {
        transformEnd: `
          // 每个图元沿其中心缩放
          // transformed = (transformed - aCenter) * uProgress + aCenter;


          // 每个图元沿其中心缩放 + 自底向上缩放显示
          float prog = (position.y + 1.) / 2.;
          float locProg = clamp((uProgress - 0.8 * prog) / 0.2, 0.0, 1.0);
          transformed = (transformed - aCenter) * locProg + aCenter;


          // 每个图元，随机沿 y 轴旋转
          // transformed = rotate(
          //   transformed,
          //   vec3(0.0, 1.0, 0.0),
          //   aRandom * (1.0 - uProgress) * PI * 3.0
          // );

          // 每个 三角形，沿 法线方向 动画
          // transformed += (1.0 - uProgress) * aRandom * normal;


          // 对 每个图元 动画
          // transformed += uProgress * aRandom * normal * (sin(uTime) * 0.5 + 0.5);
        `
      },
    
      uniforms: {
        uTime: {
          mixed: true,    // Uniform will be passed to a derivative material (MeshDepthMaterial below)
          linked: true,   // Similar as shared, but only for derivative materials, so wavingMaterial will have it's own, but share with it's shadow material
          value: 0
        },
        uProgress: {
          mixed: true,    // Uniform will be passed to a derivative material (MeshDepthMaterial below)
          linked: true,   // Similar as shared, but only for derivative materials, so wavingMaterial will have it's own, but share with it's shadow material
          value: data.progress
        },
        diffuse: {
          value: new THREE.Color(0xee3399)
        }
      }
    
    } );


    const sphere = new Mesh(geo, mat);
    sphere.castShadow = true;

    // ** customDepthMaterial，指定 该 mesh 的 depthTest 信息
    // ** 将 primitive 的 vertex 动画信息与 depthTest信息 同步
    sphere.customDepthMaterial = extendMaterial(THREE.MeshDepthMaterial, {
      template: mat
    })
    this.scene.add(sphere);

    this.tick.add((delta, elapsedTime) => {
      mat.uniforms.uTime.value = elapsedTime
    })
  }

  addPlane() {
    const geo = new PlaneGeometry(20, 20);
    const mat = new THREE.ShaderMaterial({
      side: THREE.DoubleSide,
      vertexShader: `
        varying vec2 vUv;
        void main() {
          vUv = uv;
          gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(position, 1.0);
        }
      `,
      fragmentShader: `
        varying vec2 vUv;
        void main() {
          // float color = fract(5.0 * vUv.x);
          // color = smoothstep(0.2, 0.8, color);
          // color = step(0.5, color);

          // float dist = length(vUv - vec2(0.5));
          float dist = fract(length(vUv - vec2(0.5)) * 15.0);
          float color = step(0.2, dist);

          gl_FragColor = vec4(vec3(color), 1.0);
        }
      `

    });
    const mat2 = new THREE.MeshPhongMaterial({
      color: 0xc9c9c9,
      side: THREE.DoubleSide
    });
    const plane = new Mesh(geo, mat2);
    plane.translateY(-1)
    plane.rotateX(Math.PI / 2);

    plane.receiveShadow = true;
    this.scene.add(plane);
  }

  animatePrivitivesByShaderTime() {
    const geo = new THREE.SphereGeometry(1, 32, 32)
      .toNonIndexed();

    { // 给每个 triangle 设置 radom 值
      let len = geo.attributes.position.count;
      const aRandom = new Float32Array(len * 3);
      for (let index = 0; index < len; index += 3) {
        const element = Math.random();
        aRandom[index] = element;
        aRandom[index + 1] = element;
        aRandom[index + 2] = element;
      }
      geo.setAttribute('aRandom', new THREE.BufferAttribute(aRandom, 1))
    }

    const mat = new ShaderMaterial({
      side: THREE.DoubleSide,
      // wireframe: true,
      vertexShader: `
        attribute float aRandom;
        varying vec2 vUv;
        uniform float uTime;
        void main() {
          vUv = uv;
          vec3 pos = position;

          // aRandom -> 让每个三角，起伏的 高度不同
          // pos.y += aRandom * sin((uv.x + uv.y + uTime) * 1.) * 1.1;

          pos += aRandom * normal * (sin(uTime) * 0.5 + 0.5);

          gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4( pos, 1.0 );
        }
      `,
      fragmentShader: `
        varying vec2 vUv;
        uniform float uTime;
        void main() {
          gl_FragColor = vec4(vUv, 0.0, 1.0);
        }
      `,
      uniforms: {
        uTime: {
          value: 0
        }
      }
    });

    const sphere = new Mesh(geo, mat);
    sphere.castShadow = true;
    this.scene.add(sphere);

    this.tick.add((delta, elapsedTime) => {
      mat.uniforms.uTime.value = elapsedTime
    })
  }

  addLight() {
    const spotLight = new THREE.SpotLight(0xffffff, 9);
    spotLight.castShadow = true;
    spotLight.penumbra = 0.06;
    spotLight.position.set(1.818, 0.642, 1.08);
    spotLight.shadow.mapSize.width = 2048;
    spotLight.shadow.mapSize.height = 2048; // 阴影贴图高度 
    
    this.scene.add(spotLight)

    const ambientLight = new THREE.AmbientLight(0xffffff, 1.2);
    this.scene.add(ambientLight);
  }
}


const Init: React.FC = () => {
  const containerRef = useRef(null!);

  useEffect(() => {
    const oThree = new KuriThree(containerRef.current)
    return () => {
      oThree.dispose()
    }
  }, []);

  return (
    <div className='full center' ref={containerRef}>
      
    </div>
  );
};

export default Init;
